OpenFisca-US¶
This is a working prototype of an OpenFisca-powered microsimulation model for the US tax system. Note that this is a very minimal implementation with only a few variables implemented, and figures therefore will not match aggregates.
Interface¶
The general interface is largely inherited from tools developed for OpenFisca-UK.
Populations¶
Population-level analyses can use Microsimulation.
from openfisca_us import Microsimulation
import plotly.express as px
sim = Microsimulation()
years = list(range(2018, 2028))
incomes = [sim.calc("Taxes", year).sum() for year in years]
px.line(x=years, y=incomes).update_layout(
template="plotly_white",
xaxis_title="Year",
yaxis_title="Tax Aggregate",
xaxis_tickvals=years,
)
This class also has in-built tools for marginal tax rate calculation, including handling for MTR calculation on variables of different entities (e.g. MTR of tax unit tax liability with respect to individual variables).
mtr = (
sim.deriv("Taxes", wrt="earned")
.groupby(sim.calc("earned"))
.mean()
.rolling(100)
.mean()
)
px.line(mtr[mtr.index < 1e6]).update_layout(
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Effective MTR",
showlegend=False,
)
Individuals¶
Hypothetical tax scenarios are simple to calculate with IndividualSim.
from openfisca_us import IndividualSim
import pandas as pd
single_filer = IndividualSim()
def create_single_blind_tu(*args):
sim = IndividualSim(*args, year=2021)
sim.add_person(name="person")
sim.add_taxunit(head="person", MARS="SINGLE", blind_head=True)
sim.vary("earned")
return sim
single_filer = create_single_blind_tu()
# a useful OpenFisca feature is the equal treatment of input, intermediate and output variables
# any operation can be done on either, e.g. MTR calculation wrt an intermediate variable
# like below
single_filer.vary("earned")
results = pd.DataFrame(
{
"Earned income": single_filer.calc("earned")[0],
"Taxes": single_filer.calc_deriv("Taxes", wrt="earned"),
"Standard deduction": single_filer.calc_deriv(
"standard_deduction", wrt="earned"
),
}
)
px.line(
results, x="Earned income", y=["Taxes", "Standard deduction"]
).update_layout(
yaxis_tickformat="%",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Derivative",
)
single_filer = create_single_blind_tu()
# a useful OpenFisca feature is the equal treatment of input, intermediate and output variables
# any operation can be done on either, e.g. MTR calculation wrt an intermediate variable
# like below
single_filer.vary("earned")
results = pd.DataFrame(
{
"Earned income": single_filer.calc("earned")[0],
"Taxes": single_filer.calc("Taxes")[0],
"Standard deduction": single_filer.calc("standard_deduction")[0],
"After-tax income": single_filer.calc("AfterTaxIncome")[0],
}
)
px.line(
results,
x="Earned income",
y=["Taxes", "Standard deduction", "After-tax income"],
).update_layout(
yaxis_tickprefix="$",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Amount",
)
Reforms¶
Parametric reforms can now be specified from a single YAML file.
from openfisca_us.reforms import reform_from_file
# a reform that multiplies the basic standard deduction by 10
reform = reform_from_file("tax_cut.yaml")
baseline = Microsimulation()
reformed = Microsimulation(reform)
years = list(range(2018, 2028))
revenues = [
reformed.calc("Taxes", year).sum() - baseline.calc("Taxes", year).sum()
for year in years
]
px.line(x=years, y=revenues).update_layout(
template="plotly_white",
xaxis_title="Year",
yaxis_title="Reform revenue",
xaxis_tickvals=years,
)
One of the most useful features of OpenFisca is its Python interface for reforms, allowing multi-reform analyses to be calculated procedurally in code.
from openfisca_us.reforms import parametric_reform
def increase_standard_deduction(amount):
def modify_params(parameters):
std_ded = parameters.irs.deductions.standard.amount.filer
for MARS_type in std_ded.children:
current_value = std_ded.children[MARS_type].get_at_instant(
"2020-01-01"
)
new_value = current_value + amount
std_ded.children[MARS_type].update(
period="year:2020:10", value=new_value
)
return parameters
return parametric_reform(modify_params)
results = []
# for each level of StD increase:
baseline = create_single_blind_tu()
for amount in range(1000, 200000, 5000):
# generate two hypothetical scenario simulations
reform = create_single_blind_tu(increase_standard_deduction(amount))
# add all the results as rows to a dataframe, with each row labelled with its reform info
results += [
pd.DataFrame(
{
"Earned income": baseline.calc("earned")[0],
"Taxes (Baseline)": baseline.calc("Taxes")[0],
"Taxes (Reform)": reform.calc("Taxes")[0],
"SD increase": amount,
}
)
]
results = pd.concat(results)
px.line(
results,
x="Earned income",
y=["Taxes (Baseline)", "Taxes (Reform)"],
animation_frame="SD increase",
).update_layout(
yaxis_tickprefix="$",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Amount",
)